Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

feat: add template literal types for formatted ids#594

Closed
andrewpeters9 wants to merge 4 commits intozenstackhq:devfrom
andrewpeters9:feat/template-literal-id-types
Closed

feat: add template literal types for formatted ids#594
andrewpeters9 wants to merge 4 commits intozenstackhq:devfrom
andrewpeters9:feat/template-literal-id-types

Conversation

@andrewpeters9
Copy link

@andrewpeters9 andrewpeters9 commented Jan 13, 2026

Marking this as a draft, as it still requires more work & I was wanting input from the maintainers.

Idea:
Allow for the TS types of the newly (optionally) formatted IDs to narrow to prefix_{string} as opposed to just string:

type User = {
   // new behaviour
   id: `user_{string}`;
   // old behaviour
   id: string;
}

Motivations:

  • It provides a great level of granularity to type-safety, e.g.:
const fetchUser = (userId: User["id"]) => {
   // would ensure that no random strings can enter the function
}

Concerns:

  • will be a breaking change to people's types (but not their runtime), as some (like myself) may have already been using User["id"] with the assumption that it casts to a string
    • IMO, it would be a sane default, as most people that are prefixing their IDs are generally working in repos where type-safety is crucial
    • but if you disagree, we could potentially add a flag e.g. narrowType or something (defaults to false)
  • TS performance of these ternaries

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines +2 to +11
import type {
ExtractIdFormat,
FormatToTemplateLiteral,
MapStringWithFormat,
} from '../src/utils/type-utils';

describe('FormatToTemplateLiteral', () => {
it('converts simple prefix format to template literal', () => {
expectTypeOf<FormatToTemplateLiteral<'user_%s'>>().toEqualTypeOf<`user_${string}`>();
});
Copy link
Author

@andrewpeters9 andrewpeters9 Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is a bit verbose - let me know if you'd prefer me to trim it down

Comment on lines +319 to +325
//#region Field defaults

/**
* ID generator functions that support format strings (e.g., uuid(4, "user_%s"))
*/
export const ID_GENERATOR_FUNCTIONS = ['uuid', 'cuid', 'nanoid', 'ulid'] as const;
export type IdGeneratorFunction = (typeof ID_GENERATOR_FUNCTIONS)[number];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't find a more appropriate file for this (but admittedly, seems very misplaced 😅)

Advise if you have another file in mind / or would prefer a new file

@andrewpeters9 andrewpeters9 force-pushed the feat/template-literal-id-types branch from 6aea088 to 54f8656 Compare January 13, 2026 07:57
@sanny-io
Copy link
Contributor

This would be a very cool feature. I hope everything works out in the end, because I do agree that this is a big improvement for safety. And I'm happy to see more improvements for ID formatting so quickly after its release :D

@andrewpeters9
Copy link
Author

andrewpeters9 commented Jan 14, 2026

This would be a very cool feature. I hope everything works out in the end, because I do agree that this is a big improvement for safety. And I'm happy to see more improvements for ID formatting so quickly after its release :D

Thanks for the feedback :)

Given you implemented this feature: do you think it should default to the narrower type of prefix_{string} or should it require an opt-in flag?

@sanny-io

@sanny-io
Copy link
Contributor

sanny-io commented Jan 14, 2026

@andrewpeters9 we are in a bit of a pickle at the moment because the format is not actually enforced at runtime. There's nothing stopping the user from inputting whatever ID they wanted, so the most appropriate type for the ID is indeed string, but I am of the opinion that users should not be able to do that, which is why I proposed zenstackhq/zenstack#2359.

If an ID field is readonly and has some inferable default value, then I think it should be narrowed. That makes the most sense to me because it maps 1-1 with const vs let in TypeScript. Whether that's feasible given the current constraints, I don't know.

@ymc9 ymc9 changed the base branch from main to dev January 15, 2026 03:16
@andrewpeters9
Copy link
Author

@sanny-io Pardon late reply. Before reading this I realised the same thing while thinking about the MR over the last week.

Some other thoughts:

  • if we were to enforce these types then we would also need to do a creation of migrations to ensure that the underlying data is actually prefix_{id}
  • by chance, do you think the refining of types would be better placed above the @id layer, as there may be cases where you want to create template-literal types for fields other than id's?

E.g. a rough example

model Model {
   field  String @templateLiteral(["prefix_", string])
}

And then this would be internally handled by either zod validation or whichever way emails were validated when a user would do @email

At the same time, this doesn't exactly provide us a clear pattern, as the addition of @email didn't produce migrations (and rightfully so).

See: https://zod.dev/api?id=template-literals

@sanny-io
Copy link
Contributor

sanny-io commented Feb 5, 2026

@andrewpeters9 good points you bring up.

A @templateLiteral attribute could be the right direction. There was actually recent talk in the Discord of being able to control some aspects of type generation, which is now being tracked in zenstackhq/zenstack#2350

That feature might be of interest for an implementation of template literals.

@ymc9
Copy link
Member

ymc9 commented Feb 7, 2026

Hi @andrewpeters9 , my apologies for the very late reply. Thanks for working on it, a very cool PR. However I want to put it on hold for the following reasons:

  1. It creates a coupling between the client's typing and the definition of the default value provider functions (uuid, cuid, etc.). These functions may be externalized from the ORM's core in the future and may even be extensible to user contributions.
  2. A coupling itself is not necessarily a bad thing, but I feel the value we get out of it is not big enough, as the improvements to DX are not significant enough, IMHO.

I like the idea and enjoyed reviewing the code, but I would like to wait for more requests from other developers for such improvements, and we can reconsider it later.

Appreciate the effort and thanks for the understanding!

@andrewpeters9
Copy link
Author

Hey no worries! @ymc9

& no stress at all, I only realised after opening the MR that i hadn't followed the proper protocol of posting on Discord etc.

I might just fork or patch-package the repo in the coming weeks with an implementation along the lines of @templateLiteral - I'll report back with a feature-request discussion if I've found any success with a decent API pattern.

@andrewpeters9
Copy link
Author

Closing, will report back in a few weeks with findings :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants